Passed
Pull Request — master (#7)
by Muhammad Dyas
01:26
created

ActionHandler.recordVote   C

Complexity

Conditions 11

Size

Total Lines 27
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 18
dl 0
loc 27
rs 5.4
c 0
b 0
f 0
cc 11

How to fix   Complexity   

Complexity

Complex classes like ActionHandler.recordVote often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
import {chat_v1 as chatV1} from 'googleapis/build/src/apis/chat/v1';
2
import BaseHandler from './BaseHandler';
3
import NewPollFormCard from '../cards/NewPollFormCard';
4
import {addOptionToState, getStateFromCard} from '../helpers/state';
5
import {callMessageApi} from '../helpers/api';
6
import {createDialogActionResponse, createStatusActionResponse} from '../helpers/response';
7
import PollCard from '../cards/PollCard';
8
import {PollState, Voter, Votes} from '../helpers/interfaces';
9
import AddOptionFormCard from '../cards/AddOptionFormCard';
10
import {saveVotes} from '../helpers/vote';
11
import {MAX_NUM_OF_OPTIONS} from '../config/default';
12
import ClosePollFormCard from '../cards/ClosePollFormCard';
13
14
/*
15
This list methods are used in the poll chat message
16
 */
17
interface PollAction {
18
  saveOption(): Promise<chatV1.Schema$Message>;
19
20
  getEventPollState(): PollState;
21
}
22
23
export default class ActionHandler extends BaseHandler implements PollAction {
24
  async process(): Promise<chatV1.Schema$Message> {
25
    const action = this.event.common?.invokedFunction;
26
    switch (action) {
27
      case 'start_poll':
28
        return await this.startPoll();
29
      case 'vote':
30
        return this.recordVote();
31
      case 'add_option_form':
32
        return this.addOptionForm();
33
      case 'add_option':
34
        return await this.saveOption();
35
      case 'show_form':
36
        return createDialogActionResponse(new NewPollFormCard({topic: '', choices: []}).create());
37
      case 'close_poll_form':
38
        return createDialogActionResponse(new ClosePollFormCard().create());
39
      case 'close_poll':
40
        return await this.closePoll();
41
      default:
42
        return createStatusActionResponse('Unknown action!', 'UNKNOWN');
43
    }
44
  }
45
46
  /**
47
   * Handle the custom start_poll action.
48
   *
49
   * @returns {object} Response to send back to Chat
50
   */
51
  async startPoll() {
52
    // Get the form values
53
    const formValues = this.event.common?.formInputs;
54
    const topic = formValues?.['topic']?.stringInputs?.value?.[0]?.trim() ?? '';
55
    const isAnonymous = formValues?.['is_anonymous']?.stringInputs?.value?.[0] === '1';
56
    const allowAddOption = formValues?.['allow_add_option']?.stringInputs?.value?.[0] === '1';
57
    const pollType: ClosableType = parseInt(formValues?.['type']?.stringInputs?.value?.[0] ?? '1') as ClosableType;
58
    const choices = [];
59
    const votes: Votes = {};
60
61
    for (let i = 0; i < MAX_NUM_OF_OPTIONS; ++i) {
62
      const choice = formValues?.[`option${i}`]?.stringInputs?.value?.[0]?.trim();
63
      if (choice) {
64
        choices.push(choice);
65
        votes[i] = [];
66
      }
67
    }
68
69
    if (!topic || choices.length === 0) {
70
      // Incomplete form submitted, rerender
71
      const dialog = new NewPollFormCard({
72
        topic,
73
        choices,
74
      }).create();
75
      return {
76
        actionResponse: {
77
          type: 'DIALOG',
78
          dialogAction: {
79
            dialog: {
80
              body: dialog,
81
            },
82
          },
83
        },
84
      };
85
    }
86
    const pollCard = new PollCard({
87
      topic: topic, choiceCreator: undefined,
88
      author: this.event.user,
89
      choices: choices,
90
      votes: votes,
91
      anon: isAnonymous,
92
      optionable: allowAddOption,
93
      type: pollType,
94
    }).createCardWithId();
95
    // Valid configuration, make the voting card to display in the space
96
    const message = {
97
      cardsV2: [pollCard],
98
    };
99
    const request = {
100
      parent: this.event.space?.name,
101
      requestBody: message,
102
    };
103
    const apiResponse = await callMessageApi('create', request);
104
    if (apiResponse) {
105
      return createStatusActionResponse('Poll started.', 'OK');
106
    } else {
107
      return createStatusActionResponse('Failed to start poll.', 'UNKNOWN');
108
    }
109
  }
110
111
  /**
112
   * Handle the custom vote action. Updates the state to record
113
   * the user's vote then rerenders the card.
114
   *
115
   * @returns {object} Response to send back to Chat
116
   */
117
  recordVote() {
118
    const parameters = this.event.common?.parameters;
119
    if (!(parameters?.['index'])) {
120
      throw new Error('Index Out of Bounds');
121
    }
122
    const choice = parseInt(parameters['index']);
123
    const userId = this.event.user?.name ?? '';
124
    const userName = this.event.user?.displayName ?? '';
125
    const voter: Voter = {uid: userId, name: userName};
126
    const state = this.getEventPollState();
127
128
    // Add or update the user's selected option
129
    state.votes = saveVotes(choice, voter, state.votes!, state.anon);
130
    const card = new PollCard(state);
131
    return {
132
      thread: this.event.message?.thread,
133
      actionResponse: {
134
        type: 'UPDATE_MESSAGE',
135
      },
136
      cardsV2: [card.createCardWithId()],
137
    };
138
  }
139
140
  /**
141
   * Opens and starts a dialog that allows users to add details about a contact.
142
   *
143
   * @returns {object} open a dialog.
144
   */
145
  addOptionForm() {
146
    const state = this.getEventPollState();
147
    const dialog = new AddOptionFormCard(state).create();
148
    return createDialogActionResponse(dialog);
149
  };
150
151
  /**
152
   * Handle add new option input to the poll state
153
   * the user's vote then rerenders the card.
154
   *
155
   * @returns {object} Response to send back to Chat
156
   */
157
  async saveOption(): Promise<chatV1.Schema$Message> {
158
    const userName = this.event.user?.displayName ?? '';
159
    const state = this.getEventPollState();
160
    const formValues = this.event.common?.formInputs;
161
    const optionValue = formValues?.['value']?.stringInputs?.value?.[0]?.trim() || '';
162
    addOptionToState(optionValue, state, userName);
163
164
    const cardMessage = new PollCard(state).createMessage();
165
166
    const request = {
167
      name: this.event.message!.name,
168
      requestBody: cardMessage,
169
      updateMask: 'cardsV2',
170
    };
171
    const apiResponse = await callMessageApi('update', request);
172
    if (apiResponse) {
173
      return createStatusActionResponse('Option is added', 'OK');
174
    } else {
175
      return createStatusActionResponse('Failed to add option.', 'UNKNOWN');
176
    }
177
  }
178
179
  getEventPollState(): PollState {
180
    const stateJson = getStateFromCard(this.event);
181
    if (!stateJson) {
182
      throw new ReferenceError('no valid state in the event');
183
    }
184
    return JSON.parse(stateJson);
185
  }
186
187
  async closePoll(): Promise<chatV1.Schema$Message> {
188
    const state = this.getEventPollState();
189
    state.closedTime = Date.now();
190
    const cardMessage = new PollCard(state).createMessage();
191
    const request = {
192
      name: this.event.message!.name,
193
      requestBody: cardMessage,
194
      updateMask: 'cardsV2',
195
    };
196
    const apiResponse = await callMessageApi('update', request);
197
    if (apiResponse) {
198
      return createStatusActionResponse('Poll is closed', 'OK');
199
    } else {
200
      return createStatusActionResponse('Failed to close poll.', 'UNKNOWN');
201
    }
202
  }
203
}
204